package im.composer.vapu;

import im.composer.audio.engine.InOut;
import im.composer.controller.ControllerListener;
import im.composer.controller.ControllerRecorder;
import im.composer.ds.CapSet;

import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;

import org.jaudiolibs.audioservers.AudioConfiguration;
import org.tritonus.share.midi.TMidiDevice;

import themidibus.MidiBus;
import themidibus.listener.MidiListener;

import com.thoughtworks.xstream.annotations.XStreamImplicit;

/**
 * VAPU 核心类，子类要求有无参构造函数。
 * 
 * @author David Zhang (zdl@zdl.hk)
 * 
 */
public abstract class VAPU extends InOut implements MidiDevice, Receiver, Transmitter, MidiListener, ControllerListener {

	private Receiver receiver;
	private ControllerListener ctrl_listener;
	protected ControllerRecorder controller_recorder;
	@XStreamImplicit(itemFieldName = "capabilities")
	private final CapSet capabilities = new CapSet();
	@XStreamImplicit(itemFieldName = "properties")
	private final Properties properties = new Properties();
	private transient boolean open = false;
	protected Info deviceInfo = new TMidiDevice.Info("Standard VAPU", "SinoJoy", "Yet Another VAPU", "0.0.1");

	public VAPU() {
		super(null);
	}

	public VAPU(AudioConfiguration context) {
		super(context);
	}

	@Override
	public void send(MidiMessage message, long timeStamp) {
		MidiBus.notifyListeners(this, message, timeStamp);
	}

	@Override
	protected void prepareBuffer() {
		callSources();
		if (controller_recorder != null) {
			playCommands(getTime());
		}
		super.prepareBuffer();
	}

	protected void playCommands(long t) {
		long frame_start_time = (long) (t * millisecond_per_frame);// inclusive
		long frame_end_time = (long) ((t + 1) * millisecond_per_frame);// exclusive
		if (t == 1) {
			frame_start_time = 0;// bug fix, otherwise, commands lay in t=0~t=1 will be left out.
		}
		controller_recorder.subSet(frame_start_time, frame_end_time).forEach(entry -> {
			executeCommand(entry.timeStamp, entry.command);
		});
	}

	public synchronized void startRecording() {
		if (controller_recorder == null) {
			controller_recorder = new ControllerRecorder();
		}
		controller_recorder.startRecording();
	}

	public void stopRecording() {
		if (controller_recorder == null) {
			return;
		}
		controller_recorder.stopRecording();
	}

	public boolean isRecording() {
		if (controller_recorder == null) {
			return false;
		}
		return controller_recorder.isRecording();
	}

	@Override
	public final void commandReceived(long timeStamp, String command) {
		executeCommand(timeStamp, command);
		if (isRecording()) {
			controller_recorder.recordCommand(timeStamp, command);
		}
	}

	/**
	 * 实际执行某种控制信息。本方法不应该被外部调用，外部应调用commandReceived方法。
	 * 
	 * @param timeStamp
	 *            毫秒，相对于乐曲起始时间。若为负数，则表示不支持timeStamp，该命令应当立即执行。
	 * @param command
	 */
	protected abstract void executeCommand(long timeStamp, String command);

	/**
	 * 查询此VAPU是否具有某功能，大小写不敏感。
	 * 
	 * @param cap
	 * @return
	 */
	@Override
	public boolean canDo(String cap) {
		return capabilities.contains(cap);
	}

	public boolean addCapability(String cap) {
		return capabilities.add(cap);
	}

	public boolean removeCapability(String cap) {
		return capabilities.remove(cap);
	}

	@Override
	public String[] listCapabilities() {
		return capabilities.toArray(new String[capabilities.size()]);
	}

	@Override
	public void setProperty(String key, String value) {
		properties.setProperty(key, value);
	}

	@Override
	public String getProperty(String key) {
		return properties.getProperty(key);
	}

	public ControllerListener getListener() {
		return ctrl_listener;
	}

	public void setListener(ControllerListener ctrl_listener) {
		this.ctrl_listener = ctrl_listener;
	}

	public Receiver getReceiver() {
		return receiver;
	}

	public void setReceiver(Receiver receiver) {
		this.receiver = receiver;
	}

	@Override
	public Info getDeviceInfo() {
		return deviceInfo;
	}

	@Override
	public void open() throws MidiUnavailableException {
		open = true;
	}

	@Override
	public boolean isOpen() {
		return open;
	}

	@Override
	public void close() {
		open = false;
	}

	@Override
	public long getMicrosecondPosition() {
		return (long) (getTime() * millisecond_per_frame * 1000);
	}

	@Override
	public int getMaxReceivers() {
		return 1;
	}

	@Override
	public int getMaxTransmitters() {
		return 1;
	}

	@Override
	public List<Receiver> getReceivers() {
		if (getReceiver() != null) {
			return Arrays.asList(getReceiver());
		}
		return Arrays.asList();
	}

	@Override
	public Transmitter getTransmitter() throws MidiUnavailableException {
		return this;
	}

	@Override
	public List<Transmitter> getTransmitters() {
		return Arrays.<Transmitter> asList(this);
	}

}
